<template>
  <div v-loading="loading" class="container">
    <div id="myCanvasBox" class="canvas-box">
      <div class="mask"></div>
      <canvas v-if="inited" id="myCanvas" class="canvas" :width="canvasW" :height="canvasH" @mousedown="onMousedown" @mousemove="onMousemove" @mouseup="onMouseup" @mouseleave="onMouseleave"></canvas>
    </div>
  </div>
</template>

<script>
export default {
  name: 'ImageMosaic',
  components: {},
  props: {
    url: {
      // 图片
      type: String,
    },
  },
  data() {
    return {
      loading: false,
      onDraw: false,
      inited: false,
      canvas: null,
      ctx: null,
      image: null,
      canvasW: 0,
      canvasH: 0,
      canvasL: 0,
      canvasT: 0,
      imageW: 0,
      imageH: 0,
      tileSize: 10,
      drawL: 0,
      drawT: 0,
      drawW: 0,
      drawH: 0,
      oldX: 0,
      oldY: 0,
    }
  },
  mounted() {
    this.init()
  },
  methods: {
    init() {
      // 获取canvas元素
      const box = document.getElementById('myCanvasBox')
      this.canvasW = parseFloat(box.getBoundingClientRect().width.toFixed(2))
      this.canvasH = parseFloat(box.getBoundingClientRect().height.toFixed(2))
      this.canvasL = parseFloat(box.getBoundingClientRect().left.toFixed(2))
      this.canvasT = parseFloat(box.getBoundingClientRect().top.toFixed(2))
      this.inited = true
      this.$nextTick(() => {
        this.canvas = document.getElementById('myCanvas')
        this.ctx = this.canvas.getContext('2d', { willReadFrequently: true })
        this.image = new Image()
        this.image.src = this.url
        this.image.crossOrigin = ''
        if (this.image.complete) {
          this.imageW = parseFloat(this.image.width.toFixed(2))
          this.imageH = parseFloat(this.image.height.toFixed(2))
          this.drawImage()
        } else {
          const that = this
          this.image.onload = function () {
            that.imageW = parseFloat(that.image.width.toFixed(2))
            that.imageH = parseFloat(that.image.height.toFixed(2))
            that.drawImage()
          }
        }
      })
    },
    // 绘制图片
    drawImage() {
      const canvasR = this.canvasW / this.canvasH
      const imageR = this.imageW / this.imageH
      const calculateDimensions = (canvasSize, imageSize, ratio) => {
        const scaledSize = (canvasSize * imageSize) / ratio
        return Math.round(scaledSize * 100) / 100 // 保留两位小数
      }
      if (imageR > canvasR) {
        this.drawW = this.canvasW
        this.drawH = calculateDimensions(this.drawW, this.imageH, this.imageW)
        this.drawL = 0
        this.drawT = (this.canvasH - this.drawH) / 2
      } else {
        this.drawH = this.canvasH
        this.drawW = calculateDimensions(this.drawH, this.imageW, this.imageH)
        this.drawT = 0
        this.drawL = (this.canvasW - this.drawW) / 2
      }
      // 只有在需要剪裁时才进行剪裁操作
      if (imageR !== canvasR) {
        this.ctx.beginPath()
        this.ctx.rect(this.drawL, this.drawT, this.drawW, this.drawH)
        this.ctx.clip() // 应用剪裁路径
      }
      this.ctx.drawImage(this.image, this.drawL, this.drawT, this.drawW, this.drawH)
    },
    // 鼠标按下
    onMousedown(event) {
      var mouseX = event.clientX - this.canvasL
      var mouseY = event.clientY - this.canvasT
      this.oldX = mouseX
      this.oldY = mouseY
      this.onDraw = true
    },
    // 鼠标移动
    onMousemove(event) {
      if (!this.onDraw) {
        return
      }
      var mouseX = event.clientX - this.canvasL
      var mouseY = event.clientY - this.canvasT
      this.mosaic(mouseX, mouseY)
      this.oldX = mouseX
      this.oldY = mouseY
    },
    // 鼠标抬起
    onMouseup() {
      this.onDraw = false
    },
    onMouseleave() {
      this.onDraw = false
    },
    // 获取数据
    getData() {
      const iamgeData = this.ctx.getImageData(this.drawL, this.drawT, this.drawW, this.drawH)
      const canvas = document.createElement('canvas') //创建canvas元素
      canvas.width = this.drawW
      canvas.height = this.drawH
      const ctx = canvas.getContext('2d', { willReadFrequently: true })
      ctx.putImageData(iamgeData, 0, 0)
      const data = canvas.toDataURL() //转换图片为dataURL
      return data
    },
    // 马赛克
    mosaic(x, y) {
      if (x < this.drawL || x > this.drawL + this.drawW || y < this.drawT || y > this.drawT + this.drawH) {
        return
      }
      if (x === this.oldX && y === this.oldY) {
        return
      }
      const { x: limitedX, y: limitedY } = this.getLimitPos({ x, y })
      x = limitedX
      y = limitedY

      // 如果移动距离小于 tileSize，直接处理当前区域
      if (Math.abs(x - this.oldX) <= this.tileSize && Math.abs(y - this.oldY) <= this.tileSize) {
        this.applyMosaic(x, y)
        return
      }

      // 鼠标移动过快，插值处理
      this.interpolateMosaic(x, y)
    },
    // 对指定区域应用马赛克
    applyMosaic(x, y) {
      const data = this.ctx.getImageData(x, y, this.tileSize, this.tileSize)
      const averageColor = this.getAverageColor(data)
      this.ctx.fillStyle = `rgb(${averageColor.r}, ${averageColor.g}, ${averageColor.b})`
      this.ctx.fillRect(x, y, this.tileSize, this.tileSize)
    },
    // 插值处理马赛克
    interpolateMosaic(x, y) {
      const length = Math.sqrt((x - this.oldX) ** 2 + (y - this.oldY) ** 2)
      const count = Math.ceil(length / this.tileSize) * 2

      const stepX = (x - this.oldX) / count
      const stepY = (y - this.oldY) / count

      for (let i = 0; i <= count; i++) {
        let posX = this.oldX + stepX * i
        let posY = this.oldY + stepY * i

        // 限制坐标范围
        const { x: limitedPosX, y: limitedPosY } = this.getLimitPos({ x: posX, y: posY })
        posX = limitedPosX
        posY = limitedPosY

        this.applyMosaic(posX, posY)
      }

      // 更新 oldX 和 oldY
      this.oldX = x
      this.oldY = y
    },
    getLimitPos(pox) {
      let x = pox.x
      let y = pox.y
      if (x < this.drawL) {
        x = this.drawL
      }
      if (y < this.drawT) {
        y = this.drawT
      }
      if (x + this.tileSize > this.drawL + this.drawW) {
        x = this.drawL + this.drawW - this.tileSize
      }
      if (y + this.tileSize > this.drawT + this.drawH) {
        y = this.drawT + this.drawH - this.tileSize
      }
      return { x, y }
    },
    // 计算平均颜色值
    getAverageColor(imageData) {
      let r = 0,
        g = 0,
        b = 0
      const data = imageData.data
      for (let i = 0; i < data.length; i += 4) {
        r += data[i]
        g += data[i + 1]
        b += data[i + 2]
      }
      const count = data.length / 4
      return {
        r: Math.floor(r / count),
        g: Math.floor(g / count),
        b: Math.floor(b / count),
      }
    },
  },
}
</script>
<style scoped lang="scss">
.canvas-box {
  position: relative;
  width: 100%;
  height: 450px;
  box-sizing: border-box;
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  direction: ltr;
  touch-action: none;
  text-align: left;
  background-image: url('');
  .mask {
    width: 100%;
    height: 100%;
    background: rgba(0, 0, 0, 0.3);
  }
  .canvas {
    width: 100%;
    height: 100%;
    position: absolute;
    left: 0;
    top: 0;
  }
}
.canvas {
  cursor: url(''), pointer;
  // cursor: url('') 8 8, pointer;
}
</style>
